Micron Document
πŸŽ–οΈGitΠ―Ρ€Π°πŸŽ–οΈ


Displaying Raw β€’ Download

feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/ota/dfu/LegacyDfuTransport.kt renovate/net.java.dev.jna-jna-5.x (5a8f4966) Text, 29.23 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tf0883e@fileTb4b4b4:Te6edf3SuppressTb4b4b4(
Ta5d6ff"Ta5d6ffMagicNumberTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffTooManyFunctionsTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffThrowsCountTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffSwallowedExceptionTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4,
Tb4b4b4)

Tff7b72package T7ee787org.meshtastic.feature.firmware.ota.dfu

Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787kotlinx.coroutines.CancellationException
Tff7b72import T7ee787kotlinx.coroutines.CompletableDeferred
Tff7b72import T7ee787kotlinx.coroutines.CoroutineDispatcher
Tff7b72import T7ee787kotlinx.coroutines.CoroutineScope
Tff7b72import T7ee787kotlinx.coroutines.SupervisorJob
Tff7b72import T7ee787kotlinx.coroutines.TimeoutCancellationException
Tff7b72import T7ee787kotlinx.coroutines.cancel
Tff7b72import T7ee787kotlinx.coroutines.channels.Channel
Tff7b72import T7ee787kotlinx.coroutines.coroutineScope
Tff7b72import T7ee787kotlinx.coroutines.delay
Tff7b72import T7ee787kotlinx.coroutines.flow.catch
Tff7b72import T7ee787kotlinx.coroutines.flow.first
Tff7b72import T7ee787kotlinx.coroutines.flow.launchIn
Tff7b72import T7ee787kotlinx.coroutines.flow.onEach
Tff7b72import T7ee787kotlinx.coroutines.launch
Tff7b72import T7ee787kotlinx.coroutines.withTimeout
Tff7b72import T7ee787org.meshtastic.core.ble.BleConnectionFactory
Tff7b72import T7ee787org.meshtastic.core.ble.BleConnectionState
Tff7b72import T7ee787org.meshtastic.core.ble.BleDevice
Tff7b72import T7ee787org.meshtastic.core.ble.BleScanner
Tff7b72import T7ee787org.meshtastic.core.ble.BleWriteType
Tff7b72import T7ee787org.meshtastic.core.common.util.safeCatching
Tff7b72import T7ee787org.meshtastic.feature.firmware.ota.calculateMacPlusOne
Tff7b72import T7ee787org.meshtastic.feature.firmware.ota.scanForBleDevice
Tff7b72import T7ee787kotlin.time.Duration
Tff7b72import T7ee787kotlin.time.Duration.Companion.milliseconds
Tff7b72import T7ee787kotlin.time.Duration.Companion.minutes
Tff7b72import T7ee787kotlin.time.Duration.Companion.seconds

T8b949e/**
* Kable-based transport for the Nordic **Legacy DFU** protocol (Nordic SDK 11/12 / Adafruit `BLEDfu`).
*
* Most nRF52 boards in the field β€” including the RAK4631 with the recommended Adafruit/oltaco "OTAFIX" bootloader β€”
* speak Legacy DFU rather than Secure DFU. The two protocols share nothing at the upload layer:
* - Different service & characteristic UUIDs (`1530`/`1531`/`1532` vs `FE59`/`8EC9…`).
* - Different opcodes; init packet is sent on the Packet char between two control-point writes (vs Secure's
* CREATE/PACKET/EXECUTE object flow).
* - PRN payload is bytes-received uint32 (vs Secure's offset+CRC32).
* - No CRC32 in the protocol β€” image integrity relies on the device's CRC16 in the init packet.
*
* Phase-1 buttonless trigger is shared with [SecureDfuTransport] (see `triggerButtonlessDfu` there).
*/
Tff7b72class T56d364LegacyDfuTransportTb4b4b4(
Tff7b72private Tff7b72val Te6edf3scannerTb4b4b4: Te6edf3BleScannerTb4b4b4,
Te6edf3connectionFactoryTb4b4b4: Te6edf3BleConnectionFactoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3addressTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3dispatcherTb4b4b4: Te6edf3CoroutineDispatcherTb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3DfuUploadTransport Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3transportScope Tff7b72= Te6edf3CoroutineScopeTb4b4b4(Te6edf3SupervisorJobTb4b4b4(Tb4b4b4) Tff7b72+ Te6edf3dispatcherTb4b4b4)
Tff7b72private Tff7b72val Te6edf3bleConnection Tff7b72= Te6edf3connectionFactoryTb4b4b4.Te6edf3createTb4b4b4(Te6edf3transportScopeTb4b4b4, Ta5d6ff"Ta5d6ffLegacy DFUTa5d6ff"Tb4b4b4)

T8b949e/** Receives parsed responses from the Control Point characteristic. */
Tff7b72private Tff7b72val Te6edf3notificationChannel Tff7b72= Te6edf3ChannelTff7b72<Te6edf3LegacyDfuResponseTff7b72>Tb4b4b4(Te6edf3ChannelTb4b4b4.Te6edf3UNLIMITEDTb4b4b4)

T8b949e/** Name advertised by the device in DFU mode (e.g. `4631_DFU`). Captured in [connectToDfuMode]. */
Tff7b72private Tff7b72var Te6edf3dfuAdvertisedNameTb4b4b4: Tffa657String? Tff7b72= Tff7b72null

T8b949e// ---------------------------------------------------------------------------
T8b949e// Phase 2: Connect to device in DFU mode
T8b949e// ---------------------------------------------------------------------------

T8b949e/**
* Scans for the device in DFU mode (address or address+1) and establishes the GATT connection, enabling
* notifications on the Control Point.
*
* Best-effort reads the optional DFU Version characteristic to gate against unsupported old (SDK ≀ 6) bootloaders.
*/
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffconnectToDfuModeTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ResultTff7b72<Tffa657UnitTff7b72> Tff7b72= Te6edf3safeCatching Tb4b4b4{
Tff7b72val Te6edf3dfuAddress Tff7b72= Te6edf3calculateMacPlusOneTb4b4b4(Te6edf3addressTb4b4b4)
Tff7b72val Te6edf3targetAddresses Tff7b72= Te6edf3setOfTb4b4b4(Te6edf3addressTb4b4b4, Te6edf3dfuAddressTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Scanning for DFU mode device at Tffd700$Te6edf3targetAddressesTa5d6ff...Ta5d6ff" Tb4b4b4}

Tff7b72val Te6edf3device Tff7b72=
Te6edf3scanForDevice Tb4b4b4{ Te6edf3d Tff7b72-Tff7b72> Te6edf3dTb4b4b4.Te6edf3address Tff7b72in Te6edf3targetAddresses Tb4b4b4}
Tff7b72?: Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3ConnectionFailedTb4b4b4(Ta5d6ff"Ta5d6ffDFU mode device not found. Tried: Tffd700$Te6edf3targetAddressesTa5d6ff"Tb4b4b4)

Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Found DFU mode device at Tffd700${Te6edf3deviceTb4b4b4.Te6edf3addressTffd700}Ta5d6ff (name=Tffd700${Te6edf3deviceTb4b4b4.Te6edf3nameTffd700}Ta5d6ff), connecting...Ta5d6ff" Tb4b4b4}
Te6edf3dfuAdvertisedName Tff7b72= Te6edf3deviceTb4b4b4.Te6edf3name

Te6edf3bleConnectionTb4b4b4.Te6edf3connectionState
Tb4b4b4.Te6edf3onEach Tb4b4b4{ Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Connection state β†’ Tffd700$Te6edf3itTa5d6ff" Tb4b4b4} Tb4b4b4}
Tb4b4b4.Te6edf3launchInTb4b4b4(Te6edf3transportScopeTb4b4b4)

Tff7b72val Te6edf3connected Tff7b72= Te6edf3bleConnectionTb4b4b4.Te6edf3connectAndAwaitTb4b4b4(Te6edf3deviceTb4b4b4, Te6edf3CONNECT_TIMEOUTTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3connected Tff7b72is Te6edf3BleConnectionStateTb4b4b4.Te6edf3DisconnectedTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3ConnectionFailedTb4b4b4(Ta5d6ff"Ta5d6ffFailed to connect to DFU device Tffd700${Te6edf3deviceTb4b4b4.Te6edf3addressTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}

Te6edf3bleConnectionTb4b4b4.Te6edf3profileTb4b4b4(Te6edf3LegacyDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4) Tb4b4b4{ Te6edf3service Tff7b72-Tff7b72>
Tff7b72val Te6edf3controlChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3LegacyDfuUuidsTb4b4b4.Te6edf3CONTROL_POINTTb4b4b4)

Tff7b72val Te6edf3subscribed Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3service
Tb4b4b4.Te6edf3observeTb4b4b4(Te6edf3controlCharTb4b4b4)
Tb4b4b4.Te6edf3onEach Tb4b4b4{ Te6edf3bytes Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3subscribedTb4b4b4.Te6edf3isCompletedTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Control Point subscribedTa5d6ff" Tb4b4b4}
Te6edf3subscribedTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3parsed Tff7b72= Te6edf3LegacyDfuResponseTb4b4b4.Te6edf3parseTb4b4b4(Te6edf3bytesTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Notification β†’ Tffd700$Te6edf3parsedTa5d6ff" Tb4b4b4}
Te6edf3notificationChannelTb4b4b4.Te6edf3trySendTb4b4b4(Te6edf3parsedTb4b4b4)
Tb4b4b4}
Tb4b4b4.Te6edf3catch Tb4b4b4{ Te6edf3e Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3subscribedTb4b4b4.Te6edf3isCompletedTb4b4b4) Te6edf3subscribedTb4b4b4.Te6edf3completeExceptionallyTb4b4b4(Te6edf3eTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Control Point notification errorTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3launchInTb4b4b4(Tff7b72thisTb4b4b4)

Te6edf3delayTb4b4b4(Te6edf3SUBSCRIPTION_SETTLETb4b4b4)
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3subscribedTb4b4b4.Te6edf3isCompletedTb4b4b4) Te6edf3subscribedTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3subscribedTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)

T8b949e// Best-effort DFU Version read β€” gate out unsupported old bootloaders (SDK ≀ 6).
Tff7b72val Te6edf3versionChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3LEGACY_DFU_VERSION_UUIDTb4b4b4)
Tff7b72val Te6edf3version Tff7b72=
Te6edf3safeCatching Tb4b4b4{ Te6edf3serviceTb4b4b4.Te6edf3readTb4b4b4(Te6edf3versionCharTb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3bytes Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3bytesTb4b4b4.Te6edf3size Tff7b72>Tff7b72= T79c0ff2Tb4b4b4) Tb4b4b4(Te6edf3bytesTff7b72[T79c0ff0Tff7b72]Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Te6edf3and T79c0ff0Te6edf3xFFTb4b4b4) Te6edf3or Tb4b4b4(Tb4b4b4(Te6edf3bytesTff7b72[T79c0ff1Tff7b72]Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Te6edf3and T79c0ff0Te6edf3xFFTb4b4b4) Te6edf3shl T79c0ff8Tb4b4b4) Tff7b72else Tff7b72-T79c0ff1
Tb4b4b4}
Tb4b4b4.Te6edf3getOrElse Tb4b4b4{ Tff7b72-T79c0ff1 Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: DFU Version characteristic = Tffd700$Te6edf3versionTa5d6ff (-1 β‡’ absent / unreadable)Ta5d6ff" Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3version Tff7b72in T79c0ff1.Tb4b4b4.Te6edf3MIN_SUPPORTED_DFU_VERSION Tff7b72- T79c0ff1Tb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3LegacyDfuExceptionTb4b4b4.Te6edf3UnsupportedBootloaderTb4b4b4(Te6edf3versionTb4b4b4)
Tb4b4b4}

Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Connected and ready (Tffd700${Te6edf3deviceTb4b4b4.Te6edf3addressTffd700}Ta5d6ff)Ta5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e// ---------------------------------------------------------------------------
T8b949e// Phase 3: Init packet transfer (.dat)
T8b949e// ---------------------------------------------------------------------------

T8b949e/**
* Sends the legacy DFU init packet using the SDK 7+ extended-init flow:
* 1. `START_DFU [APP]` β†’ device prepares.
* 2. Image sizes `[0u32, 0u32, appSize_u32]` on the Packet characteristic.
* 3. `INIT_PARAMS_START` β†’ init bytes on Packet β†’ `INIT_PARAMS_COMPLETE`.
*
* The legacy init packet for an APP image is typically 14 bytes (SDK 7) or 32 bytes (SDK 11 with signature). Any
* `.dat` significantly larger than that almost certainly belongs to a Secure DFU build that has been mis-packaged
* for a legacy bootloader β€” we surface that with a helpful error rather than letting the device reject it.
*
* The init packet is bracketed by START/COMPLETE, but the upload itself is intermixed with image sizes. To match
* Nordic's library, the [initPacket] argument here is the legacy init bytes; the firmware [transferFirmware] method
* needs to be called next to provide the actual image (and the size we include here must match its length).
*
* Because `transferInitPacket` is called before [transferFirmware], we don't yet know the firmware size when
* sending image sizes. To keep the [DfuUploadTransport] contract clean we instead send image sizes lazily inside
* [transferFirmware]'s START phase. **This method only writes START_DFU + brackets the init packet.** Any
* outstanding image-size write happens at the start of [transferFirmware].
*/
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8fftransferInitPacketTb4b4b4(Te6edf3initPacketTb4b4b4: Te6edf3ByteArrayTb4b4b4)Tb4b4b4: Te6edf3ResultTff7b72<Tffa657UnitTff7b72> Tff7b72= Te6edf3safeCatching Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3initPacketTb4b4b4.Te6edf3size Tff7b72> Te6edf3MAX_REASONABLE_LEGACY_INIT_SIZETb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3LegacyDfuExceptionTb4b4b4.Te6edf3InitPacketNotLegacyTb4b4b4(Te6edf3initPacketTb4b4b4.Te6edf3sizeTb4b4b4)
Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Stashing init packet (Tffd700${Te6edf3initPacketTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff bytes) for transfer in Phase 4.Ta5d6ff" Tb4b4b4}
Te6edf3pendingInitPacket Tff7b72= Te6edf3initPacket
Tb4b4b4}

T8b949e/** Init packet stashed by [transferInitPacket]; flushed at the start of [transferFirmware]. */
Tff7b72private Tff7b72var Te6edf3pendingInitPacketTb4b4b4: Te6edf3ByteArray? Tff7b72= Tff7b72null

T8b949e// ---------------------------------------------------------------------------
T8b949e// Phase 4: Firmware transfer (.bin)
T8b949e// ---------------------------------------------------------------------------

T8b949e/**
* Drives the full upload sequence (START, init-packet brackets, PRN setup, firmware stream, validate, activate).
*
* Sequence details:
* 1. `START_DFU [0x04]` (APP image only).
* 2. Image sizes payload on Packet char: `[0u32, 0u32, firmware.size_u32]`.
* 3. Await START response.
* 4. `INIT_PARAMS_START`, init bytes on Packet, `INIT_PARAMS_COMPLETE`. Await init response.
* 5. `PRN_REQ [PRN_LE16]`. (No response.)
* 6. `RECEIVE_FIRMWARE_IMAGE`. (No response.)
* 7. Stream firmware in MTU-sized chunks. Every PRN packets, await `PacketReceipt(bytesReceived)` and verify count.
* 8. After last byte, await final response for `RECEIVE_FIRMWARE_IMAGE`.
* 9. `VALIDATE`, await response.
* 10. `ACTIVATE_AND_RESET` β€” device reboots; write may fail with disconnect, treat as success.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffLongMethodTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8fftransferFirmwareTb4b4b4(Te6edf3firmwareTb4b4b4: Te6edf3ByteArrayTb4b4b4, Te6edf3onProgressTb4b4b4: Te6edf3suspend Tb4b4b4(Tffa657FloatTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4)Tb4b4b4: Te6edf3ResultTff7b72<Tffa657UnitTff7b72> Tff7b72=
Te6edf3safeCatching Tb4b4b4{
Tff7b72val Te6edf3initPacket Tff7b72=
Te6edf3pendingInitPacket
Tff7b72?: Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3TransferFailedTb4b4b4(Ta5d6ff"Ta5d6fftransferInitPacket must be called before transferFirmwareTa5d6ff"Tb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Starting upload (init=Tffd700${Te6edf3initPacketTb4b4b4.Te6edf3sizeTffd700}Ta5d6ffB, firmware=Tffd700${Te6edf3firmwareTb4b4b4.Te6edf3sizeTffd700}Ta5d6ffB)...Ta5d6ff" Tb4b4b4}

T8b949e// ── 1. START_DFU + image sizes on Packet, then response ─────────────
Te6edf3writeControlPointTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3START_DFUTb4b4b4, Te6edf3LegacyDfuImageTypeTb4b4b4.Te6edf3APPLICATIONTb4b4b4)Tb4b4b4)
Te6edf3writePacketTb4b4b4(Te6edf3legacyImageSizesPayloadTb4b4b4(Te6edf3appSize Tff7b72= Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4)Tb4b4b4)
Te6edf3requireSuccessTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3START_DFUTb4b4b4, Te6edf3awaitResponseTb4b4b4(Te6edf3START_RESPONSE_TIMEOUTTb4b4b4)Tb4b4b4)

T8b949e// ── 2. INIT_PARAMS_START β†’ init bytes on Packet β†’ INIT_PARAMS_COMPLETE β†’ response ──
Te6edf3writeControlPointTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3INIT_DFU_PARAMSTb4b4b4, Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3INIT_PARAMS_STARTTb4b4b4)Tb4b4b4)
Te6edf3writePacketChunkedTb4b4b4(Te6edf3initPacketTb4b4b4)
Te6edf3writeControlPointTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3INIT_DFU_PARAMSTb4b4b4, Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3INIT_PARAMS_COMPLETETb4b4b4)Tb4b4b4)
Te6edf3requireSuccessTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3INIT_DFU_PARAMSTb4b4b4, Te6edf3awaitResponseTb4b4b4(Te6edf3COMMAND_TIMEOUTTb4b4b4)Tb4b4b4)

T8b949e// Bump the BLE link to high-throughput mode (~7.5 ms interval) before streaming.
T8b949e// Default Android intervals (~30-50 ms) starve the link during sustained DFU and trigger LSTO. Mirrors
T8b949e// Nordic LegacyDfuImpl.java requestConnectionPriority(CONNECTION_PRIORITY_HIGH).
Tff7b72val Te6edf3highPriorityRequested Tff7b72= Te6edf3bleConnectionTb4b4b4.Te6edf3requestHighConnectionPriorityTb4b4b4(Tb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: requestHighConnectionPriority -> Tffd700$Te6edf3highPriorityRequestedTa5d6ff" Tb4b4b4}

T8b949e// ── 3. PRN setup ────────────────────────────────────────────────────
Te6edf3writeControlPointTb4b4b4(Te6edf3legacyPrnRequestPayloadTb4b4b4(Te6edf3PRN_INTERVAL_PACKETSTb4b4b4)Tb4b4b4)

T8b949e// ── 4. RECEIVE_FIRMWARE_IMAGE ──────────────────────────────────────
Te6edf3writeControlPointTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3RECEIVE_FIRMWARE_IMAGETb4b4b4)Tb4b4b4)

T8b949e// ── 5. Stream firmware ─────────────────────────────────────────────
Te6edf3streamFirmwareTb4b4b4(Te6edf3firmwareTb4b4b4, Te6edf3onProgressTb4b4b4)

T8b949e// ── 6. Final RECEIVE_FIRMWARE_IMAGE response ────────────────────────
Te6edf3requireSuccessTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3RECEIVE_FIRMWARE_IMAGETb4b4b4, Te6edf3awaitResponseTb4b4b4(Te6edf3VALIDATE_TIMEOUTTb4b4b4)Tb4b4b4)

T8b949e// ── 7. VALIDATE ────────────────────────────────────────────────────
Te6edf3writeControlPointTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3VALIDATETb4b4b4)Tb4b4b4)
Te6edf3requireSuccessTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3VALIDATETb4b4b4, Te6edf3awaitResponseTb4b4b4(Te6edf3VALIDATE_TIMEOUTTb4b4b4)Tb4b4b4)

T8b949e// ── 8. ACTIVATE_AND_RESET ──────────────────────────────────────────
T8b949e// Device may reset before the GATT write ACK lands β€” treat any write failure / disconnect as success.
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Sending ACTIVATE_AND_RESET (disconnect during write is expected)Ta5d6ff" Tb4b4b4}
Te6edf3runCatching Tb4b4b4{ Te6edf3writeControlPointTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3ACTIVATE_AND_RESETTb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3LoggerTb4b4b4.Te6edf3iTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: ACTIVATE write reported failure (expected on reset)Ta5d6ff" Tb4b4b4} Tb4b4b4}

Te6edf3onProgressTb4b4b4(T79c0ff1fTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Upload complete, device rebooting into new firmware.Ta5d6ff" Tb4b4b4}
Tb4b4b4}

T8b949e/**
* Stream [firmware] to the Packet characteristic, awaiting a [LegacyDfuResponse.PacketReceipt] every
* [PRN_INTERVAL_PACKETS] packets and verifying the bytes-received count.
*
* Watches the connection state in parallel with the write loop; if the link drops mid-stream we cancel the write
* coroutine and surface a [DfuException.ConnectionFailed] immediately rather than waiting indefinitely for a write
* that will never complete.
*/

T8b949e/**
* Determine the per-packet size for firmware streaming.
*
* Returns [LEGACY_DFU_PACKET_SIZE] (20) by default for safety against stock Nordic / pre-2.1 OTAFIX bootloaders
* that overrun their flash buffer on larger writes. When the device advertises an OTAFIX-2.1+ name (`<board>_DFU`
* suffix per https://github.com/oltaco/Adafruit_nRF52_Bootloader_OTAFIX#changes-in-otafix-21) and the connection
* has negotiated a larger ATT MTU, returns that MTU βˆ’ 3 (ATT header overhead) capped at 244 bytes.
*/
Tff7b72private Tff7b72fun Td2a8ffcomputeStreamPacketSizeTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657Int Tb4b4b4{
Tff7b72val Te6edf3name Tff7b72= Te6edf3dfuAdvertisedNameTb4b4b4.Te6edf3orEmptyTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3isOtafix21 Tff7b72= Te6edf3nameTb4b4b4.Te6edf3endsWithTb4b4b4(Te6edf3OTAFIX_NAME_SUFFIXTb4b4b4, Te6edf3ignoreCase Tff7b72= Tff7b72trueTb4b4b4)
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3isOtafix21Tb4b4b4) Tff7b72return Te6edf3LEGACY_DFU_PACKET_SIZE
Tff7b72val Te6edf3negotiated Tff7b72=
Te6edf3bleConnectionTb4b4b4.Te6edf3maximumWriteValueLengthTb4b4b4(Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITHOUT_RESPONSETb4b4b4) Tff7b72?: Tff7b72return Te6edf3LEGACY_DFU_PACKET_SIZE
Tff7b72return Te6edf3negotiatedTb4b4b4.Te6edf3coerceInTb4b4b4(Te6edf3LEGACY_DFU_PACKET_SIZETb4b4b4, Te6edf3MAX_HIGH_MTU_PACKET_SIZETb4b4b4)
Tb4b4b4}

Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffCyclomaticComplexMethodTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffNestedBlockDepthTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffLongMethodTa5d6ff"Tb4b4b4)
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffstreamFirmwareTb4b4b4(Te6edf3firmwareTb4b4b4: Te6edf3ByteArrayTb4b4b4, Te6edf3onProgressTb4b4b4: Te6edf3suspend Tb4b4b4(Tffa657FloatTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4) Tb4b4b4{
T8b949e// Default: 20-byte ATT packets (the only size accepted by stock Nordic / Adafruit pre-OTAFIX-2.1
T8b949e// bootloaders). Sending larger packets to those bootloaders overruns the flash-write buffer and
T8b949e// silently bricks the device. See .agent_refs/Android-DFU-Library/.../BaseCustomDfuImpl.java:417.
T8b949e//
T8b949e// OTAFIX 2.1+ explicitly supports high-MTU packets ("Enables larger DFU packets for improved
T8b949e// throughput when supported by the client" per the OTAFIX README). It re-uses the standard
T8b949e// `<board>_DFU` advertising-name suffix as a marker for the 2.1 feature set, so we opportunistically
T8b949e// bump the packet size to the negotiated ATT MTU (minus 3 for the ATT header) when we see that name.
Tff7b72val Te6edf3mtu Tff7b72= Te6edf3computeStreamPacketSizeTb4b4b4(Tb4b4b4)
Tff7b72var Te6edf3offset Tff7b72= T79c0ff0
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{
Ta5d6ff"Ta5d6ffLegacy DFU: Streaming Tffd700${Te6edf3firmwareTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff bytes with packet size Tffd700$Te6edf3mtuTa5d6ff Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff(advertised='Tffd700${Te6edf3dfuAdvertisedName Tff7b72?: Ta5d6ff"Tff7b72?Ta5d6ff"Tffd700}Ta5d6ff')Ta5d6ff"
Tb4b4b4}

Te6edf3coroutineScope Tb4b4b4{
T8b949e// Trip-wire: cancels the streaming coroutine the moment Kable observes a disconnect.
Tff7b72val Te6edf3watcher Tff7b72= Te6edf3launch Tb4b4b4{
Tff7b72val Te6edf3state Tff7b72= Te6edf3bleConnectionTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3first Tb4b4b4{ Tffa657it Tff7b72is Te6edf3BleConnectionStateTb4b4b4.Te6edf3Disconnected Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Link dropped mid-stream at offset Tffd700$Te6edf3offsetTa5d6ff/Tffd700${Te6edf3firmwareTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff (state=Tffd700$Te6edf3stateTa5d6ff)Ta5d6ff" Tb4b4b4}
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3ConnectionFailedTb4b4b4(Ta5d6ff"Ta5d6ffBLE link dropped mid-upload at byte Tffd700$Te6edf3offsetTa5d6ff/Tffd700${Te6edf3firmwareTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}

Tff7b72try Tb4b4b4{
Tff7b72var Te6edf3packetsSincePrn Tff7b72= T79c0ff0
Tff7b72var Te6edf3bytesAtLastPrn Tff7b72= T79c0ff0L
Te6edf3bleConnectionTb4b4b4.Te6edf3profileTb4b4b4(Te6edf3LegacyDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4, Te6edf3timeout Tff7b72= Te6edf3STREAM_TIMEOUTTb4b4b4) Tb4b4b4{ Te6edf3service Tff7b72-Tff7b72>
Tff7b72val Te6edf3packetChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3LEGACY_DFU_PACKET_UUIDTb4b4b4)
Tff7b72while Tb4b4b4(Te6edf3offset Tff7b72< Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3end Tff7b72= Te6edf3minOfTb4b4b4(Te6edf3offset Tff7b72+ Te6edf3mtuTb4b4b4, Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4)
Tff7b72try Tb4b4b4{
Te6edf3serviceTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3packetCharTb4b4b4, Te6edf3firmwareTb4b4b4.Te6edf3copyOfRangeTb4b4b4(Te6edf3offsetTb4b4b4, Te6edf3endTb4b4b4)Tb4b4b4, Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITHOUT_RESPONSETb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3CancellationExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{
Ta5d6ff"Ta5d6ffLegacy DFU: Write CANCELLED at offset Tffd700$Te6edf3offsetTa5d6ff/Tffd700${Te6edf3firmwareTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff cause=Tffd700${Te6edf3eTb4b4b4.Te6edf3causeTffd700}Ta5d6ff"
Tb4b4b4}
Tff7b72throw Te6edf3e
Tb4b4b4} Tff7b72catch Tb4b4b4(Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4) Te6edf3eTb4b4b4: Te6edf3ThrowableTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Write FAILED at offset Tffd700$Te6edf3offsetTa5d6ff/Tffd700${Te6edf3firmwareTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff: Tffd700${Te6edf3eTb4b4b4.Te6edf3messageTffd700}Ta5d6ff" Tb4b4b4}
Tff7b72throw Te6edf3e
Tb4b4b4}
Te6edf3offset Tff7b72= Te6edf3end
Te6edf3packetsSincePrnTff7b72+Tff7b72+

Tff7b72if Tb4b4b4(Te6edf3packetsSincePrn Tff7b72>Tff7b72= Te6edf3PRN_INTERVAL_PACKETS Tff7b72&Tff7b72& Te6edf3offset Tff7b72< Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Awaiting PRN at offset Tffd700$Te6edf3offsetTa5d6ff" Tb4b4b4}
Tff7b72val Te6edf3receipt Tff7b72=
Tff7b72try Tb4b4b4{
Te6edf3awaitPacketReceiptTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3CancellationExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{
Ta5d6ff"Ta5d6ffLegacy DFU: awaitPacketReceipt CANCELLED at offset Tffd700$Te6edf3offsetTa5d6ff cause=Tffd700${Te6edf3eTb4b4b4.Te6edf3causeTffd700}Ta5d6ff"
Tb4b4b4}
Tff7b72throw Te6edf3e
Tb4b4b4}
Tff7b72val Te6edf3expected Tff7b72= Te6edf3offsetTb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3receiptTb4b4b4.Te6edf3bytesReceived Tff7b72!Tff7b72= Te6edf3expectedTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3LegacyDfuExceptionTb4b4b4.Te6edf3PacketReceiptMismatchTb4b4b4(Te6edf3expectedTb4b4b4, Te6edf3receiptTb4b4b4.Te6edf3bytesReceivedTb4b4b4)
Tb4b4b4}
Te6edf3bytesAtLastPrn Tff7b72= Te6edf3receiptTb4b4b4.Te6edf3bytesReceived
Te6edf3packetsSincePrn Tff7b72= T79c0ff0
Te6edf3onProgressTb4b4b4(Te6edf3offsetTb4b4b4.Te6edf3toFloatTb4b4b4(Tb4b4b4) Tff7b72/ Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Streamed Tffd700$Te6edf3offsetTa5d6ff/Tffd700${Te6edf3firmwareTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff bytes (lastPRN=Tffd700$Te6edf3bytesAtLastPrnTa5d6ff)Ta5d6ff" Tb4b4b4}
Tb4b4b4} Tff7b72finally Tb4b4b4{
Te6edf3watcherTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e// ---------------------------------------------------------------------------
T8b949e// Abort & teardown
T8b949e// ---------------------------------------------------------------------------

T8b949e/**
* Send `RESET` to the device, instructing it to discard any in-progress transfer and reboot. Best-effort β€” the
* device may disconnect before the write ACK lands; that's expected.
*/
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffabortTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3safeCatching Tb4b4b4{
Te6edf3writeControlPointTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3RESETTb4b4b4)Tb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: RESET sent.Ta5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Failed to send RESET (device may already be disconnected)Ta5d6ff" Tb4b4b4} Tb4b4b4}
Tb4b4b4}

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffcloseTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3safeCatching Tb4b4b4{ Te6edf3bleConnectionTb4b4b4.Te6edf3disconnectTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Error during disconnectTa5d6ff" Tb4b4b4} Tb4b4b4}
Te6edf3transportScopeTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}

T8b949e// ---------------------------------------------------------------------------
T8b949e// Low-level GATT helpers
T8b949e// ---------------------------------------------------------------------------

Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffwriteControlPointTb4b4b4(Te6edf3payloadTb4b4b4: Te6edf3ByteArrayTb4b4b4) Tb4b4b4{
Te6edf3bleConnectionTb4b4b4.Te6edf3profileTb4b4b4(Te6edf3LegacyDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4) Tb4b4b4{ Te6edf3service Tff7b72-Tff7b72>
Tff7b72val Te6edf3controlChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3LegacyDfuUuidsTb4b4b4.Te6edf3CONTROL_POINTTb4b4b4)
Te6edf3serviceTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3controlCharTb4b4b4, Te6edf3payloadTb4b4b4, Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITH_RESPONSETb4b4b4)
Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffwritePacketTb4b4b4(Te6edf3payloadTb4b4b4: Te6edf3ByteArrayTb4b4b4) Tb4b4b4{
Te6edf3bleConnectionTb4b4b4.Te6edf3profileTb4b4b4(Te6edf3LegacyDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4) Tb4b4b4{ Te6edf3service Tff7b72-Tff7b72>
Tff7b72val Te6edf3packetChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3LEGACY_DFU_PACKET_UUIDTb4b4b4)
Te6edf3serviceTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3packetCharTb4b4b4, Te6edf3payloadTb4b4b4, Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITHOUT_RESPONSETb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e/** Write [data] to the Packet char in 20-byte chunks. Legacy DFU bootloaders cap packet size at 20 bytes. */
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffwritePacketChunkedTb4b4b4(Te6edf3dataTb4b4b4: Te6edf3ByteArrayTb4b4b4) Tb4b4b4{
Te6edf3bleConnectionTb4b4b4.Te6edf3profileTb4b4b4(Te6edf3LegacyDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4) Tb4b4b4{ Te6edf3service Tff7b72-Tff7b72>
Tff7b72val Te6edf3packetChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3LEGACY_DFU_PACKET_UUIDTb4b4b4)
Tff7b72var Te6edf3pos Tff7b72= T79c0ff0
Tff7b72while Tb4b4b4(Te6edf3pos Tff7b72< Te6edf3dataTb4b4b4.Te6edf3sizeTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3end Tff7b72= Te6edf3minOfTb4b4b4(Te6edf3pos Tff7b72+ Te6edf3LEGACY_DFU_PACKET_SIZETb4b4b4, Te6edf3dataTb4b4b4.Te6edf3sizeTb4b4b4)
Te6edf3serviceTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3packetCharTb4b4b4, Te6edf3dataTb4b4b4.Te6edf3copyOfRangeTb4b4b4(Te6edf3posTb4b4b4, Te6edf3endTb4b4b4)Tb4b4b4, Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITHOUT_RESPONSETb4b4b4)
Te6edf3pos Tff7b72= Te6edf3end
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffawaitResponseTb4b4b4(Te6edf3timeoutTb4b4b4: Te6edf3DurationTb4b4b4)Tb4b4b4: Te6edf3LegacyDfuResponse Tff7b72= Tff7b72try Tb4b4b4{
Te6edf3withTimeoutTb4b4b4(Te6edf3timeoutTb4b4b4) Tb4b4b4{
T8b949e// Drain any stray PRNs that arrive before the response we want.
Tff7b72while Tb4b4b4(Tff7b72trueTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3r Tff7b72= Te6edf3notificationChannelTb4b4b4.Te6edf3receiveTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3r Tff7b72!is Te6edf3LegacyDfuResponseTb4b4b4.Te6edf3PacketReceiptTb4b4b4) Tff7b72returnTf0883e@withTimeout Te6edf3r
Tb4b4b4}
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffUNREACHABLE_CODETa5d6ff"Tb4b4b4)
Te6edf3errorTb4b4b4(Ta5d6ff"Ta5d6ffunreachableTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3_Tb4b4b4: Te6edf3TimeoutCancellationExceptionTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3TimeoutTb4b4b4(Ta5d6ff"Ta5d6ffNo response from Legacy DFU Control Point after Tffd700$Te6edf3timeoutTa5d6ff"Tb4b4b4)
Tb4b4b4}

Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffawaitPacketReceiptTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3LegacyDfuResponseTb4b4b4.Te6edf3PacketReceipt Tff7b72= Tff7b72try Tb4b4b4{
Te6edf3withTimeoutTb4b4b4(Te6edf3COMMAND_TIMEOUTTb4b4b4) Tb4b4b4{
Tff7b72while Tb4b4b4(Tff7b72trueTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3r Tff7b72= Te6edf3notificationChannelTb4b4b4.Te6edf3receiveTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3r Tff7b72is Te6edf3LegacyDfuResponseTb4b4b4.Te6edf3PacketReceiptTb4b4b4) Tff7b72returnTf0883e@withTimeout Te6edf3r
Tff7b72if Tb4b4b4(Te6edf3r Tff7b72is Te6edf3LegacyDfuResponseTb4b4b4.Te6edf3FailureTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3LegacyDfuExceptionTb4b4b4.Te6edf3ProtocolErrorTb4b4b4(Te6edf3rTb4b4b4.Te6edf3requestOpcodeTb4b4b4, Te6edf3rTb4b4b4.Te6edf3statusTb4b4b4)
Tb4b4b4}
T8b949e// Stray Success or Unknown β†’ ignore.
Tb4b4b4}
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffUNREACHABLE_CODETa5d6ff"Tb4b4b4)
Te6edf3errorTb4b4b4(Ta5d6ff"Ta5d6ffunreachableTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3_Tb4b4b4: Te6edf3TimeoutCancellationExceptionTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3TimeoutTb4b4b4(Ta5d6ff"Ta5d6ffNo packet receipt notification after Tffd700$Te6edf3COMMAND_TIMEOUTTa5d6ff"Tb4b4b4)
Tb4b4b4}

Tff7b72private Tff7b72fun Td2a8ffrequireSuccessTb4b4b4(Te6edf3expectedOpcodeTb4b4b4: Tffa657ByteTb4b4b4, Te6edf3responseTb4b4b4: Te6edf3LegacyDfuResponseTb4b4b4) Tb4b4b4{
Tff7b72when Tb4b4b4(Te6edf3responseTb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3LegacyDfuResponseTb4b4b4.Te6edf3Success Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3responseTb4b4b4.Te6edf3requestOpcode Tff7b72!Tff7b72= Te6edf3expectedOpcodeTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3TransferFailedTb4b4b4(
Ta5d6ff"Ta5d6ffLegacy DFU response opcode mismatch: expected Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff0xTffd700${Te6edf3expectedOpcodeTb4b4b4.Te6edf3toUByteTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)Tb4b4b4.Te6edf3padStartTb4b4b4(T79c0ff2Tb4b4b4, Ta5d6ff'0'Tb4b4b4)Tffd700}Ta5d6ff, Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ffgot 0xTffd700${Te6edf3responseTb4b4b4.Te6edf3requestOpcodeTb4b4b4.Te6edf3toUByteTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)Tb4b4b4.Te6edf3padStartTb4b4b4(T79c0ff2Tb4b4b4, Ta5d6ff'0'Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}

Tff7b72is Te6edf3LegacyDfuResponseTb4b4b4.Te6edf3Failure Tff7b72-Tff7b72>
Tff7b72throw Te6edf3LegacyDfuExceptionTb4b4b4.Te6edf3ProtocolErrorTb4b4b4(Te6edf3responseTb4b4b4.Te6edf3requestOpcodeTb4b4b4, Te6edf3responseTb4b4b4.Te6edf3statusTb4b4b4)

Tff7b72else Tff7b72-Tff7b72>
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3TransferFailedTb4b4b4(
Ta5d6ff"Ta5d6ffUnexpected Legacy DFU response for opcode 0xTffd700${Te6edf3expectedOpcodeTb4b4b4.Te6edf3toUByteTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)Tffd700}Ta5d6ff: Tffd700$Te6edf3responseTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e// ---------------------------------------------------------------------------
T8b949e// Scanning
T8b949e// ---------------------------------------------------------------------------

Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffscanForDeviceTb4b4b4(Te6edf3predicateTb4b4b4: Tb4b4b4(Te6edf3BleDeviceTb4b4b4) Tff7b72-Tff7b72> Tffa657BooleanTb4b4b4)Tb4b4b4: Te6edf3BleDevice? Tff7b72= Te6edf3scanForBleDeviceTb4b4b4(
Te6edf3scanner Tff7b72= Te6edf3scannerTb4b4b4,
Te6edf3tag Tff7b72= Ta5d6ff"Ta5d6ffLegacy DFUTa5d6ff"Tb4b4b4,
Te6edf3serviceUuid Tff7b72= Te6edf3LegacyDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4,
Te6edf3retryCount Tff7b72= Te6edf3SCAN_RETRY_COUNTTb4b4b4,
Te6edf3retryDelay Tff7b72= Te6edf3SCAN_RETRY_DELAYTb4b4b4,
Te6edf3predicate Tff7b72= Te6edf3predicateTb4b4b4,
Tb4b4b4)

T8b949e// ---------------------------------------------------------------------------
T8b949e// Constants
T8b949e// ---------------------------------------------------------------------------

Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3CONNECT_TIMEOUT Tff7b72= T79c0ff1T79c0ff5.Te6edf3seconds
Tff7b72private Tff7b72val Te6edf3COMMAND_TIMEOUT Tff7b72= T79c0ff3T79c0ff0.Te6edf3seconds
Tff7b72private Tff7b72val Te6edf3START_RESPONSE_TIMEOUT Tff7b72= T79c0ff3T79c0ff0.Te6edf3seconds
Tff7b72private Tff7b72val Te6edf3VALIDATE_TIMEOUT Tff7b72= T79c0ff6T79c0ff0.Te6edf3seconds
Tff7b72private Tff7b72val Te6edf3SUBSCRIPTION_SETTLE Tff7b72= T79c0ff5T79c0ff0T79c0ff0.Te6edf3milliseconds
Tff7b72private Tff7b72const Tff7b72val Te6edf3SCAN_RETRY_COUNT Tff7b72= T79c0ff3
Tff7b72private Tff7b72val Te6edf3SCAN_RETRY_DELAY Tff7b72= T79c0ff2.Te6edf3seconds

T8b949e/**
* Wall-clock budget for a full firmware streaming session. Must comfortably exceed the upload duration for the
* largest expected image at the slowest realistic Legacy DFU throughput (~1-3 KB/s with 20-byte packets). The
* per-receipt and per-write watchdogs inside the loop catch real stalls; this cap is just a safety net so a
* hung profile block can't sit forever.
*/
Tff7b72private Tff7b72val Te6edf3STREAM_TIMEOUT Tff7b72= T79c0ff1T79c0ff5.Te6edf3minutes

T8b949e/**
* Packet-receipt-notification interval (packets between flow-control ACKs). Higher values mean fewer
* notification round-trips per byte and therefore faster throughput, at the cost of a slightly longer recovery
* window if a packet is dropped (we have to wait until the next PRN boundary to detect the gap).
*
* Set to 30 per the explicit recommendation from the Adafruit OTAFIX bootloader maintainer
* (https://github.com/oltaco/Adafruit_nRF52_Bootloader_OTAFIX#recommended-ota-dfu-settings β€” "Number of
* packets: 30"), which is the bootloader Meshtastic nRF52 devices ship with. Nordic's reference library
* defaults to 12; values above ~60 are not recommended for any host. Empirically 30 yields ~3x the throughput
* of PRN=10 on RAK4631 / OTAFIX without provoking OPERATION_FAILED on the bootloader's flash-write path.
*/
Tff7b72internal Tff7b72const Tff7b72val Te6edf3PRN_INTERVAL_PACKETS Tff7b72= T79c0ff3T79c0ff0

T8b949e/**
* Default Legacy DFU packet size (20 bytes β€” the original ATT_MTU minus the 3-byte ATT header).
*
* Stock Nordic / pre-OTAFIX-2.1 bootloaders only support this size; sending larger packets to those bootloaders
* overruns the flash-write buffer and silently bricks the device. For OTAFIX 2.1+ bootloaders (detected via
* `OTAFIX_NAME_SUFFIX`), [computeStreamPacketSize] bumps the per-packet size up to the negotiated MTU (capped
* by [MAX_HIGH_MTU_PACKET_SIZE]) for a ~12Γ— throughput win.
*/
Tff7b72internal Tff7b72const Tff7b72val Te6edf3LEGACY_DFU_PACKET_SIZE Tff7b72= T79c0ff2T79c0ff0

T8b949e/**
* Cap on the high-MTU packet size used when an OTAFIX-2.1+ bootloader is detected. The largest ATT MTU the BLE
* 5.0 LE Data Length extension can give us is 247 bytes (244 of payload), and Adafruit's own flash accumulation
* buffer is 240 bytes, so capping at 244 keeps each write to one ATT PDU and one flash-write boundary.
*/
Tff7b72internal Tff7b72const Tff7b72val Te6edf3MAX_HIGH_MTU_PACKET_SIZE Tff7b72= T79c0ff2T79c0ff4T79c0ff4

T8b949e/**
* Suffix used by the OTAFIX 2.1+ bootloader on every supported board's DFU-mode advertising name (e.g.
* `4631_DFU`, `T114_DFU`, `XIAO_DFU`). The 2.0 release advertised generic `AdaDFU`/board names without this
* suffix, so its presence is a reliable in-band signal that high-MTU is supported.
*/
Tff7b72internal Tff7b72const Tff7b72val Te6edf3OTAFIX_NAME_SUFFIX Tff7b72= Ta5d6ff"Ta5d6ff_DFUTa5d6ff"

T8b949e/** Minimum DFU Version we support; older bootloaders use the SDK ≀ 6 single-shot init flow. */
Tff7b72private Tff7b72const Tff7b72val Te6edf3MIN_SUPPORTED_DFU_VERSION Tff7b72= T79c0ff5

T8b949e/**
* Init packets larger than this are almost certainly Secure-DFU shaped (signed CBOR β‰ˆ 100-300 bytes) rather
* than legacy (14 B SDK 7 / 32 B SDK 11). 256 leaves comfortable headroom while still catching the obvious
* misuse case where a Secure `.dat` is fed into the Legacy path.
*/
Tff7b72internal Tff7b72const Tff7b72val Te6edf3MAX_REASONABLE_LEGACY_INIT_SIZE Tff7b72= T79c0ff2T79c0ff5T79c0ff6
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.12s